using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Diagnostics;
using System.Globalization;
using Modelica.Annotations;


namespace DrawToolsLib {
    /// <summary>
    /// Base class for all graphics objects.
    /// </summary>
    public abstract class GraphicsBase : DrawingVisual, ICloneable {
        #region Static members

        public static GraphicsBase GetGraphicsByAnnotation(GraphicItem gi) {
            if (gi is Line) { return new GraphicsPolyLine(gi as Line); }
            if (gi is Polygon) { return new GraphicsPolygon(gi as Polygon); }
            if (gi is Rectangle) { return new GraphicsRectangle(gi as Rectangle); }
            if (gi is Ellipse) { return new GraphicsEllipse(gi as Ellipse); }
            if (gi is Text) { return new GraphicsText(gi as Text); }

            throw new Exception("Error, unknown Graphic Item type");
        }

        #endregion

        #region Class Members

        protected virtual double graphicsLineWidth { get; set; }
        protected virtual Color graphicsObjectColor { get; set; }
        
        public static readonly DependencyProperty ParentCSProperty;
        protected double graphicsActualScale;
        protected bool selected;
        protected bool dragged;

        protected const double HitTestWidth = 8.0;

        protected const double HandleSize = 10.0;

        // Objects used to draw handles. I want to keep them all time program runs,
        // therefore I don't use pens, because actual pen size can change.

        // external rectangle
        static SolidColorBrush handleBrush1 = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
        // middle
        static SolidColorBrush handleBrush2 = new SolidColorBrush(Color.FromArgb(255, 200, 200, 200));

        // border Pen
        static protected Pen borderPenBase;

        #endregion Class Members

        #region Constructor

        static GraphicsBase() {
            borderPenBase = new Pen(Brushes.Black, 1);
            borderPenBase.DashStyle = new DashStyle(new double[] { 4, 6 }, 1);

            // ParentCS
            PropertyMetadata metaData = new PropertyMetadata(
                new CoordinateSystem(),
                new PropertyChangedCallback(ParentCSChanged));

            ParentCSProperty = DependencyProperty.Register(
                "ParentCS", typeof(CoordinateSystem), typeof(GraphicsBase),
                metaData);
        }

        protected GraphicsBase()
            : base() {
            //ParentCS = new CoordinateSystem();
            Id = this.GetHashCode();
        }

        #endregion Constructor

        #region Properties

        public bool IsSelected {
            get {
                return selected;
            }
            set {
                selected = value;

                RefreshDrawing();
            }
        }

        public bool IsDragged {
            get {
                return dragged;
            }
            set {
                dragged = value;

                RefreshDrawing();
            }
        }

        public double LineWidth {
            get {
                return graphicsLineWidth;
            }

            set {
                graphicsLineWidth = value;

                RefreshDrawing();
            }
        }

        public Color ObjectColor {
            get {
                return graphicsObjectColor;
            }

            set {
                graphicsObjectColor = value;

                RefreshDrawing();
            }
        }

        public double ActualScale {
            get {
                return graphicsActualScale;
            }

            set {
                graphicsActualScale = value;

                RefreshDrawing();
            }
        }

        protected double ActualLineWidth {
            get {
                return graphicsActualScale <= 0 ? graphicsLineWidth : graphicsLineWidth / graphicsActualScale;
            }
        }


        protected double LineHitTestWidth {
            get {
                // Ensure that hit test area is not too narrow
                return Math.Max(8.0, ActualLineWidth);
            }
        }

        protected Pen borderPen {
            get {
                borderPenBase.Thickness = 1 / ((graphicsActualScale <= 0) ? 1 : graphicsActualScale);
                return borderPenBase;
            }
        }

        protected Extent ex {
            get {
                return this.ParentCS.Extent;
            }
            set {
                this.ParentCS.Extent = value;
            }
        }

        public abstract GraphicItem Source { get; }


        /// <summary>
        /// Object ID
        /// Allows to write Undo - Redo functions and don't care about
        /// objects order in the list.
        /// </summary>
        public int Id { get; set; }

        #endregion Properties

        #region ParentCS DP

        /// <summary>
        /// Returns ParentCS.
        /// </summary>
        public CoordinateSystem ParentCS {
            get {
                return (CoordinateSystem)GetValue(ParentCSProperty);
            }
            set {
                SetValue(ParentCSProperty, value);
            }
        }

        /// <summary>
        /// Callback function called when ParentCS dependency property is changed
        /// </summary>
        static void ParentCSChanged(DependencyObject property, DependencyPropertyChangedEventArgs args) {
            GraphicsBase gb = property as GraphicsBase;

            // reset history for new Icon
            gb.ResetGraphics();
        }

        protected virtual void ResetGraphics() {
        }

        #endregion

        #region Abstract Methods and Properties

        /// <summary>
        /// Returns number of handles
        /// </summary>
        public abstract int HandleCount {
            get;
        }


        /// <summary>
        /// Hit test, should be overwritten in derived classes.
        /// </summary>
        public abstract bool Contains(Point point);

        /// <summary>
        /// Get handle point by 1-based number
        /// </summary>
        public abstract Point GetHandle(int handleNumber);

        /// <summary>
        /// Hit test.
        /// Return value: -1 - no hit
        ///                0 - hit anywhere
        ///                > 1 - handle number
        /// </summary>
        public abstract int MakeHitTest(Point point);


        /// <summary>
        /// Test whether object intersects with rectangle
        /// </summary>
        public abstract bool IntersectsWith(Rect rectangle);

        /// <summary>
        /// Move object
        /// </summary>
        public abstract void Move(double deltaX, double deltaY);


        /// <summary>
        /// Move handle to the point
        /// </summary>
        public abstract void MoveHandleTo(Point point, int handleNumber);

        /// <summary>
        /// Get cursor for the handle
        /// </summary>
        public abstract Cursor GetHandleCursor(int handleNumber);

        #endregion Abstract Methods and Properties

        #region Virtual Methods

        /// <summary>
        /// Normalize object.
        /// Call this function in the end of object resizing,
        /// </summary>
        public virtual void Normalize() {
            // Empty implementation is OK for classes which don't require
            // normalization, like line.
            // Normalization is required for rectangle-based classes.
        }

        /// <summary>
        /// Implements actual drawing code.
        /// 
        /// Call GraphicsBase.Draw in the end of every derived class Draw 
        /// function to draw tracker if necessary.
        /// </summary>
        public virtual void Draw(DrawingContext drawingContext) {
            if (IsSelected) {
                DrawBorder(drawingContext);

                if (!IsDragged) {
                    DrawTracker(drawingContext);
                }
            }
        }

        /// <summary>
        /// Draw tracker for selected object.
        /// </summary>
        public virtual void DrawTracker(DrawingContext drawingContext) {
            for (int i = 1; i <= HandleCount; i++) {
                DrawTrackerRectangle(drawingContext, GetHandleRectangle(i));
            }
        }

        /// <summary>
        /// Draw boreder for selected object.
        /// </summary>
        public virtual void DrawBorder(DrawingContext drawingContext) {

        }


        /// <summary>
        /// Dump (for debugging)
        /// </summary>
        [Conditional("DEBUG")]
        public virtual void Dump() {
            Trace.WriteLine(this.GetType().Name);

            Trace.WriteLine("ID = " + Id.ToString(CultureInfo.InvariantCulture) +
                "   Selected = " + selected.ToString(CultureInfo.InvariantCulture));

            Trace.WriteLine("objectColor = " + ColorToDisplay(graphicsObjectColor) +
                "  lineWidth = " + DoubleForDisplay(graphicsLineWidth));
        }


        #endregion Virtual Methods

        #region Other Methods

        /// <summary>
        /// Draw tracker rectangle
        /// </summary>
        static void DrawTrackerRectangle(DrawingContext drawingContext, Rect rectangle) {
            // External rectangle
            drawingContext.DrawRectangle(handleBrush1, null, rectangle);

            // Middle
            drawingContext.DrawRectangle(handleBrush2, null,
                new Rect(rectangle.Left + rectangle.Width / 5,
                         rectangle.Top + rectangle.Height / 5,
                         rectangle.Width * 3 / 5,
                         rectangle.Height * 3 / 5));
        }


        /// <summary>
        /// Refresh drawing.
        /// Called after change if any object property.
        /// </summary>
        public void RefreshDrawing() {
            DrawingContext dc = this.RenderOpen();

            Draw(dc);

            dc.Close();
        }

        /// <summary>
        /// Get handle rectangle by 1-based number
        /// </summary>
        public Rect GetHandleRectangle(int handleNumber) {
            Point point = GetHandle(handleNumber);

            // Handle rectangle should have constant size, except of the case
            // when line is too width.
            double size = Math.Max(HandleSize / graphicsActualScale, ActualLineWidth * 1.1);

            return new Rect(point.X - size / 2, point.Y - size / 2,
                size, size);
        }

        /// <summary>
        /// Helper function used for Dump
        /// </summary>
        static string DoubleForDisplay(double value) {
            return ((float)value).ToString("f2", CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Helper function used for Dump
        /// </summary>
        static string ColorToDisplay(Color value) {
            //return "A:" + value.A.ToString() +
            return "R:" + value.R.ToString(CultureInfo.InvariantCulture) +
                   " G:" + value.G.ToString(CultureInfo.InvariantCulture) +
                   " B:" + value.B.ToString(CultureInfo.InvariantCulture);
        }


        #endregion Other Methods

        #region ICloneable Members

        public virtual object Clone() {
            throw new NotImplementedException();
        }

        #endregion
    }
}
